package com.oa.gform.rebind;
		
import java.io.PrintWriter;

import com.google.gwt.core.ext.Generator;
import com.google.gwt.core.ext.GeneratorContext;
import com.google.gwt.core.ext.TreeLogger;
import com.google.gwt.core.ext.UnableToCompleteException;
import com.google.gwt.core.ext.typeinfo.JClassType;
import com.google.gwt.core.ext.typeinfo.JField;
import com.google.gwt.core.ext.typeinfo.TypeOracle;
import com.google.gwt.user.rebind.ClassSourceFileComposerFactory;
import com.google.gwt.user.rebind.SourceWriter;
import com.oa.gform.client.bean.binder.BeanBinder;
import com.oa.gform.client.bean.binder.Mapper;
import com.oa.gform.client.bean.binder.Mapping;
import com.oa.gform.client.ui.HasGItemsDataSource;
import com.oa.gform.client.ui.datasource.GDataSourceManager;
import com.oa.gform.client.validator.GValidatorNotEmpty;
import com.oa.gform.rebind.GDataSource.LoadOn;

public class GFormGenerator extends Generator {

	@Override
	public String generate(TreeLogger logger, GeneratorContext context, String typeName) throws UnableToCompleteException {
		
		return createClass(logger,context,typeName);
	}
	
	private final String createClass(TreeLogger logger, GeneratorContext context, String typeName) throws UnableToCompleteException {
		// Estraggo il  TypeOracle dal contesto.
		// Il TypeOracle contiene la descrizione della classe.
		//System.out.println("inizio generazione");
		String generatedClassName = "";
		TypeOracle typeOracle = context.getTypeOracle();
		
		try {
			// We retrieve the JClassType object and the package name for the
			// original type from the typeOracle
			JClassType originalType = typeOracle.getType(typeName);
			JClassType[] gformBinderTypes = originalType.getImplementedInterfaces();
			JClassType[] typesArgs = gformBinderTypes[0].isParameterized().getTypeArgs();

			// Estraggo i tipi passati all'interfaccia
			JClassType uiRootType = typesArgs[0];
			JClassType uiOwnerType = typesArgs[1];
			
			// Creo lo stringBuffer per scriver il metodo createAndBindGForm
			StringBuffer createAndBindGForm = new StringBuffer();
			createAndBindGForm.append("\tpublic " + uiRootType.getQualifiedSourceName() + " createAndBindGForm(final "+ uiOwnerType.getQualifiedSourceName() + " form) {\n");
			//createAndBindGForm.append("\t\tSystem.out.println(\"SSSSSSSSSSSSSSSSSSSSSSSSSSss\");\n");
			for (JField field : uiOwnerType.getFields()) {
				if (implementGField(field)) {
					createAndBindGForm.append("\t\t form.addGField(form." + field.getName() + ");\n");
				}
				GValidators gValidators = field.getAnnotation(GValidators.class);
				GDataSource gDatasource = field.getAnnotation(GDataSource.class);
				GBeanBinder gBeanBinder = field.getAnnotation(GBeanBinder.class);
				if (gValidators!=null) {
					injectValidators(field, gValidators, createAndBindGForm);
				}
				if (gDatasource!=null) {
					injectDatasource(field, gDatasource, createAndBindGForm);
				}
				if (gBeanBinder!=null) {
					injectBeanBinder(field, gBeanBinder, createAndBindGForm);
				}
			}
			createAndBindGForm.append("\t 	return form;");
			createAndBindGForm.append("\t}\n");
			String packageName = originalType.getPackage().getName();
			String originalClassName = originalType.getSimpleSourceName();
			generatedClassName = originalClassName + "Generated";
			
			// get print writer that receives the sourceObject code
			PrintWriter printWriter = null;
			printWriter = context.tryCreate(logger, packageName, generatedClassName);
			// print writer if null, sourceObject code has ALREADY been generated, return
			if (printWriter == null) {
				return  packageName + "." + generatedClassName;
			}
			ClassSourceFileComposerFactory composer = null;
			composer = new ClassSourceFileComposerFactory(packageName, generatedClassName);
			//composer.setSuperclass(originalType.getQualifiedSourceName());
			composer.addImplementedInterface(originalType.getQualifiedSourceName());
			
			SourceWriter sourceWriter = composer.createSourceWriter(context, printWriter);
			//String constructor = ""; 
			//constructor += "this.className = \"" + escape(originalClassName) + "\";\n";
			//GeneratorHelper.generateConstructor(sourceWriter, typeName, generatedClassName, constructor);
			//sourceWriter.outdent();
			sourceWriter.println(createAndBindGForm.toString());
			sourceWriter.println("\n}"); // Chiusura della classe
			
			//sourceWriter.println("String  appo = \"" + resourceName + "\";");
			context.commit(logger, printWriter);
			//System.out.println("Commited generator");
			return packageName + "." + generatedClassName;
		} catch (Exception e) {
			e.printStackTrace();
			logger.log(TreeLogger.ERROR, "Errore durante la generazione della classe: " + generatedClassName, e);
			return null;
		}
	}
	
	/**
	 * Inject il bena binder
	 * @param field
	 * @param gBeanBinder
	 * @param createAndBindGForm
	 */
	private void injectBeanBinder(JField field, GBeanBinder gBeanBinder, StringBuffer buffer) {
		JClassType[] types = field.getType().isParameterized().getTypeArgs();
		JClassType formType = types [0];
		JClassType beanType = types [1];
		String beanInstanceType = beanType.getQualifiedSourceName();
		String formInstanceType = formType.getQualifiedSourceName();
		String formBeanBinder = "form." +  field.getName(); 
		String formBeanName = "form." + gBeanBinder.beanInstanceName();
		buffer.append("\t\t" + formBeanBinder + " = new " + BeanBinder.class.getCanonicalName() + "<" + formInstanceType+ "," +beanInstanceType + ">();\n");
		if (formBeanName.equals("form.")) {
			buffer.append("\t\t" + formBeanBinder + ".setBeanInstance(new " + beanInstanceType + "());\n");
			formBeanName = formBeanBinder + ".getBeanInstance()"; 
		} else {
			buffer.append("\t\t" + formBeanBinder + ".setBeanInstance(form." + gBeanBinder.beanInstanceName() + ");\n");
		}
		String mapping = Mapping.class.getCanonicalName();
		String mapper = Mapper.class.getCanonicalName();
		// Inizio il ciclo su tutti i tipi del bean e creo i realtivi mapper
		for (JField jBeanField : beanType.getFields()) {
			String gFieldName = jBeanField.getName().substring(0,1).toUpperCase() + jBeanField.getName().substring(1);
			String setterMethodName = "setFieldValue";
			String getterMethodName = "getFieldValue";
			
			buffer.append("\t\t" + formBeanBinder + ".addMapping(" +
							"new " + mapping + "(form.fld"+gFieldName +",\"" + gFieldName.toUpperCase() +  "\","+
								"new " + mapper +"() {" + 
									"public void formToBean() {" +
										formBeanName +".set" + gFieldName + "(form.fld" + gFieldName + "." + getterMethodName + "());" +
									"}" +
									"public void beanToForm() {" +
										"form.fld" +  gFieldName +"." + setterMethodName+ "(" + formBeanName+ ".get" + gFieldName + "());" +
									"}" +
								"}" +
							")" +
						  ");\n"
			);
		}
		/*
		comuneBinder.addMapping(new Mapping(fldField1, "Field1",new Mapper(){
			public void formToBean() {
				beanComune.setField1(fldField1.getText());
			}
			public void beanToForm() {
				fldField1.setText(beanComune.getField1());
			}
		}));*/
		
		//"" +
		//	"addDataSource(" + gDatasource.dataSource().getCanonicalName() + ".class, new " + gDatasource.dataSource().getCanonicalName() + "());\n");
		//field.getType();
	}

	/***
	 * Inject i datasorce delle field con item
	 * @param field
	 * @param gDatasource
	 * @param buffer
	 */
	private void injectDatasource(JField field, GDataSource gDatasource, StringBuffer buffer) {
		String dataSourceManagerClass = GDataSourceManager.class.getCanonicalName();
		buffer.append("\t\t" + dataSourceManagerClass + ".addDataSource(" + gDatasource.dataSource().getCanonicalName() + ".class, new " + gDatasource.dataSource().getCanonicalName() + "());\n");
		buffer.append("\t\t ((" + HasGItemsDataSource.class.getCanonicalName()+ ")form." + field.getName() + ").setGItemsDatasource("+ dataSourceManagerClass  + ".getDataSource(" + gDatasource.dataSource().getCanonicalName() +".class));\n");
		if (gDatasource.loadingMode().equals(LoadOn.CREATE)) {
			buffer.append("\t\t ((" + HasGItemsDataSource.class.getCanonicalName()+ ")form." + field.getName()  + ").loadData();\n");
		}

	}

	/***
	 * Inject dei validatori nella field
	 * @param field
	 * @param gValidators
	 * @param buffer
	 */
	private void injectValidators(JField field,GValidators gValidators,StringBuffer buffer  ) {
		buffer.append("\t\t form." + field.getName() + ".setGFieldLabel(form." + gValidators.GFieldLabelName() +");\n");
		if (gValidators.NotEmpty()) {
			buffer.append("\t\t form." + field.getName() + ".addGValidator(new " + GValidatorNotEmpty.class.getCanonicalName() +"());\n");
		}
		//TODO Implementare inject di tutti i validators
	}
	
	/***
	 * Restituisce true se la classa passata implementa l'interfaccia JField
	 * @param field
	 * @return
	 */
	private boolean implementGField(JField field ) {
		JClassType[] interfaces = field.getType().isClassOrInterface().getImplementedInterfaces();
		for (JClassType jClassType : interfaces) {
			if (jClassType.getName().equals("GField")) return true;
		}
		return false;
	}
	
}
/*
 * 	
//UiField annotationUiField = field.getAnnotation(UiField.class);
//field.getType().isClassOrInterface().findNestedType("com.oa.gform.client.ui.GField");

GWidget gWidgetAnnotation = field.getAnnotation(@UiField.class);
if (gWidgetAnnotation!=null) {
	String gFormMethodName = "getGWidget";
	//System.err.println(field.getType().getSimpleSourceName());
	if (field.getType().getSimpleSourceName().equals("GEntityMapping")) {
		gFormMethodName = "getEntityMapping";
	}
	if (gWidgetAnnotation.name().equals("")) {
		sourceInitIsGWidgetMethod.append("\t" + field.getName() + " = " + "(" + field.getType().getQualifiedSourceName() + ")this." + gFormMethodName + "(\"" +  field.getName() + "\");\n");
	} else {
		//sourceInitIsGWidget.append(field.getType().getQualifiedSourceName() + " " + gWidgetAnnotation.name() + ";\n");
		sourceInitIsGWidgetMethod.append("\t" +  field.getName() + " = " + "(" + field.getType().getQualifiedSourceName() + ")this." + gFormMethodName + "(\"" + gWidgetAnnotation.name() + "\");\n");
	}
}
// Se il tipo deriva da GForm
if (field.getType() instanceof JRealClassType) {
	if (((JRealClassType)field.getType()).getSuperclass()!=null){
		if (((JRealClassType)field.getType()).getSuperclass().getSimpleSourceName().equals("GForm")) {
			//allora aggiugo  la parent form
			sourceInitIsGWidgetMethod.append("\tif (" + field.getName() + "!=null) " + field.getName() + ".setParentGForm(this);\n");					
		}
	}
}
*/


//if (!originalType.isAnnotationPresent(GFormTemplate.class)){
//	return null;
//}
//GFormTemplate annotation = originalType.getAnnotation(GFormTemplate.class);
//JField[] jFileds = originalType.getFields();
//System.err.println(sourceInitIsGWidgetMethod.toString());


//String resourceName = (annotation!=null ? annotation.filename() : "");
//RenderModeEnum renderMode = (annotation!=null ? annotation.renderMode(): null);

